#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Copyright (c) 2025 Huawei Device Co., Ltd.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

import logging
import re
import json
import os
from utils import utils_data, ErrorInfoBuilder

logger = logging.getLogger('my_logger')


class TCChecker:
    testcase_regex = {
        'arkts': re.compile(r'^\s*it\s*\(\s*["\'](.*?)["\']\s*,'),
        'javascript': re.compile(r'^\s*it\s*\(\s*["\'](.*?)["\']\s*,'),
        'c++': re.compile(r'^\s*HWTEST_F\(|\s*HWTEST\(')
    }
    tc_number_regex = re.compile(r'@tc\.number\s*\s*(.*)')
    tc_name_regex = re.compile(r'@tc\.name\s*\s*(.*)')
    tc_desc_regex = re.compile(r'@tc\.desc\s*\s*(.*)')
    tc_level_regex = re.compile(r'@tc.level\s*\s*(.*)')
    tc_size_regex = re.compile(r'@tc\.size\s*\s*(.*)')
    tc_type_regex = re.compile(r'@tc\.type\s*\s*(.*)')
    language_map = {
        '.test.ets': 'arkts',
        '.test.js': 'javascript',
        '.cpp': 'c++'
    }
    allowed_levels = ['Level0', 'Level1', 'Level2', 'Level3', 'Level4']
    allowed_sizes = ['LargeTest', 'MediumTest', 'SmallTest']
    allowed_types = ['Function', 'Performance', 'Power', 'Reliability', 'Security', 'Global', 'Compatibility', 'User',
                     'Standard', 'Safety', 'Resilience']

    @classmethod
    def get_tc_numbers(cls, lines):
        tc_numbers = []
        for i, line in enumerate(lines):
            tc_number_match = cls.tc_number_regex.search(line)
            if tc_number_match:
                tc_num = tc_number_match.group(1).strip()
                tc_numbers.append((i, tc_num))
                logger.debug(f"Extracted tc.number: {tc_num} at line {i + 1}")
        return tc_numbers

    @classmethod
    def check_duplicate_tc_numbers_in_file(cls, lines, relative_path, language):
        tc_numbers = cls.get_tc_numbers(lines)
        seen_numbers = set()
        reported_numbers = set()
        has_error = False
        for line_num, tc_number in tc_numbers:
            if tc_number in seen_numbers and tc_number not in reported_numbers:
                error_info = ErrorInfoBuilder() \
                    .set_buggy_file_path(relative_path) \
                    .set_defect_type("XTS.CHECK.TCNUMBER.01") \
                    .set_description(f"测试用例编号@tc.number {tc_number} 在本文件中重复") \
                    .set_main_buggy_line(line_num + 1) \
                    .set_main_buggy_code(lines[line_num].strip()) \
                    .set_language(language) \
                    .build()
                utils_data.write_error_to_json_file(error_info)
                logger.info(json.dumps(error_info, indent=4, ensure_ascii=False))
                has_error = True
                reported_numbers.add(tc_number)
            else:
                seen_numbers.add(tc_number)
        return has_error

    @classmethod
    def get_sibling_files(cls, file_entity, language):
        parent_dir = os.path.dirname(file_entity.full_path)
        target_file_path = os.path.normpath(file_entity.full_path)
        sibling_files = []
        for root, _, files in os.walk(parent_dir):
            for file in files:
                full_path = os.path.join(root, file)
                normalized_path = os.path.normpath(full_path)
                if normalized_path != target_file_path:
                    for suffix in cls.language_map.keys():
                        if normalized_path.endswith(suffix):
                            sibling_files.append(normalized_path)
                            logger.debug(f"Found sibling file: {normalized_path}")
                            break
        return sibling_files

    @classmethod
    def check_duplicate_tc_numbers_with_siblings(cls, lines, file_entity, language):
        target_tc_numbers = cls.get_tc_numbers(lines)
        target_tc_number_set = set([tc_number for _, tc_number in target_tc_numbers])
        logger.debug(f"Target file tc.numbers: {target_tc_number_set}")
        sibling_files = cls.get_sibling_files(file_entity, language)
        reported_numbers = set()
        has_error = False
        for sibling_file in sibling_files:
            try:
                with open(sibling_file, 'r', encoding='utf-8') as file:
                    sibling_lines = file.readlines()
                    sibling_tc_numbers = cls.get_tc_numbers(sibling_lines)
                    sibling_tc_number_set = set([tc_number for _, tc_number in sibling_tc_numbers])
                    logger.debug(f"Sibling file {sibling_file} tc.numbers: {sibling_tc_number_set}")
                    common_numbers = target_tc_number_set.intersection(sibling_tc_number_set)
                    for common_number in common_numbers:
                        if common_number not in reported_numbers:
                            for line_num, tc_number in target_tc_numbers:
                                if tc_number == common_number:
                                    sibling_file_name = os.path.basename(sibling_file)
                                    error_info = ErrorInfoBuilder() \
                                        .set_buggy_file_path(file_entity.file_path) \
                                        .set_defect_type("XTS.CHECK.TCNUMBER.01") \
                                        .set_description(f"测试用例编号@tc.number{common_number} 与 {sibling_file_name} 中的编号重复") \
                                        .set_main_buggy_line(line_num + 1) \
                                        .set_main_buggy_code(lines[line_num].strip()) \
                                        .set_language(language) \
                                        .build()
                                    utils_data.write_error_to_json_file(error_info)
                                    logger.info(json.dumps(error_info, indent=4, ensure_ascii=False))
                                    has_error = True
                                    reported_numbers.add(common_number)
                                    break
            except Exception as e:
                logger.error(f"Error reading file {sibling_file}: {e}")
        return has_error

    @classmethod
    def check_tc_number_in_range(cls, lines, start, end, tc_number_regex, language, relative_path):
        testcase_line = lines[end].strip()
        found_tc_number = False
        for i in range(end - 1, start - 1, -1):
            tc_number_match = tc_number_regex.search(lines[i])
            if tc_number_match:
                found_tc_number = True
                break
        if not found_tc_number:
            error_info = ErrorInfoBuilder() \
                .set_buggy_file_path(relative_path) \
                .set_defect_type("XTS.CHECK.TCNUMBER.01") \
                .set_description("测试用例编号的注释@tc.number字段必填") \
                .set_main_buggy_line(end + 1) \
                .set_main_buggy_code(testcase_line) \
                .set_language(language) \
                .build()
            utils_data.write_error_to_json_file(error_info)
            logger.info(json.dumps(error_info, indent=4, ensure_ascii=False))
            return True
        return False

    @classmethod
    def check_tc_name_in_range(cls, lines, start, end, tc_name_regex, language, relative_path):
        testcase_line = lines[end].strip()
        found_tc_name = False
        tc_name_line_num = None
        for i in range(end - 1, start - 1, -1):
            tc_name_match = tc_name_regex.search(lines[i])
            if tc_name_match:
                found_tc_name = True
                tc_name_line_num = i
                tc_name = tc_name_match.group(1).replace(" ", "").strip(':')  # 去掉所有空格和冒号
                if language == "arkts" or language == "javascript":
                    testcase_first_param_regex = re.compile(r'it\s*\(\s*["\']([^"\']+)["\']')
                    testcase_first_param_match = testcase_first_param_regex.search(testcase_line)
                    if testcase_first_param_match:
                        testcase_first_param = testcase_first_param_match.group(1).strip()
                        if tc_name != testcase_first_param:
                            error_info = ErrorInfoBuilder() \
                                .set_buggy_file_path(relative_path) \
                                .set_defect_type("XTS.CHECK.TCNAME.01") \
                                .set_description("测试用例名称的注释@tc.name字段必填，且与测试方法名保持一致") \
                                .set_main_buggy_line(tc_name_line_num + 1) \
                                .set_main_buggy_code(lines[tc_name_line_num].strip()) \
                                .set_language(language) \
                                .build()
                            utils_data.write_error_to_json_file(error_info)
                            logger.info(json.dumps(error_info, indent=4, ensure_ascii=False))
                            return True
                elif language == "c++":
                    testcase_second_param_regex = re.compile(r'HWTEST_F\(\s*[^,]+,\s*([^,]+)')
                    testcase_second_param_match = testcase_second_param_regex.search(testcase_line)
                    if testcase_second_param_match:
                        testcase_second_param = testcase_second_param_match.group(1).strip()
                        if tc_name != testcase_second_param:
                            error_info = ErrorInfoBuilder() \
                                .set_buggy_file_path(relative_path) \
                                .set_defect_type("XTS.CHECK.TCNAME.01") \
                                .set_description("测试用例名称的注释@tc.name字段必填，且与测试方法名保持一致") \
                                .set_main_buggy_line(tc_name_line_num + 1) \
                                .set_main_buggy_code(lines[tc_name_line_num].strip()) \
                                .set_language(language) \
                                .build()
                            utils_data.write_error_to_json_file(error_info)
                            logger.info(json.dumps(error_info, indent=4, ensure_ascii=False))
                            return True
        if not found_tc_name:
            error_info = ErrorInfoBuilder() \
                .set_buggy_file_path(relative_path) \
                .set_defect_type("XTS.CHECK.TCNAME.01") \
                .set_description("测试用例名称的注释@tc.name字段必填") \
                .set_main_buggy_line(end + 1) \
                .set_main_buggy_code(testcase_line) \
                .set_language(language) \
                .build()
            utils_data.write_error_to_json_file(error_info)
            logger.info(json.dumps(error_info, indent=4, ensure_ascii=False))
            return True
        return False

    @classmethod
    def check_tc_desc_in_range(cls, lines, start, end, tc_desc_regex, language, relative_path):
        testcase_line = lines[end].strip()
        found_tc_desc = False
        for i in range(end - 1, start - 1, -1):
            tc_desc_match = tc_desc_regex.search(lines[i])
            if tc_desc_match:
                found_tc_desc = True
                break
        if not found_tc_desc:
            error_info = ErrorInfoBuilder() \
                .set_buggy_file_path(relative_path) \
                .set_defect_type("XTS.CHECK.TCDESC.01") \
                .set_description("测试用例描述的注释@tc.desc字段必填") \
                .set_main_buggy_line(end + 1) \
                .set_main_buggy_code(testcase_line) \
                .set_language(language) \
                .build()
            utils_data.write_error_to_json_file(error_info)
            logger.info(json.dumps(error_info, indent=4, ensure_ascii=False))
            return True
        return False

    @classmethod
    def check_tc_level_in_range(cls, lines, start, end, tc_level_regex, language, relative_path):
        testcase_line = lines[end].strip()
        found_tc_level = False
        tc_level_line_num = None
        for i in range(end - 1, start - 1, -1):
            tc_level_match = tc_level_regex.search(lines[i])
            if tc_level_match:
                found_tc_level = True
                tc_level_line_num = i
                tc_level = tc_level_match.group(1).replace(" ", "").strip(':')
                if tc_level not in cls.allowed_levels:
                    error_info = ErrorInfoBuilder() \
                        .set_buggy_file_path(relative_path) \
                        .set_defect_type("XTS.CHECK.TCLEVEL.01") \
                        .set_description("测试用例级别注释@tc.level字段必填，且值在枚举范围内") \
                        .set_main_buggy_line(tc_level_line_num + 1) \
                        .set_main_buggy_code(lines[tc_level_line_num].strip()) \
                        .set_language(language) \
                        .build()
                    utils_data.write_error_to_json_file(error_info)
                    logger.info(json.dumps(error_info, indent=4, ensure_ascii=False))
                    return True
                break

        if not found_tc_level:
            error_info = ErrorInfoBuilder() \
                .set_buggy_file_path(relative_path) \
                .set_defect_type("XTS.CHECK.TCLEVEL.01") \
                .set_description("测试用例级别注释@tc.level字段必填") \
                .set_main_buggy_line(end + 1) \
                .set_main_buggy_code(testcase_line) \
                .set_language(language) \
                .build()
            utils_data.write_error_to_json_file(error_info)
            logger.info(json.dumps(error_info, indent=4, ensure_ascii=False))
            return True
        return False

    @classmethod
    def check_tc_size_in_range(cls, lines, start, end, tc_size_regex, language, relative_path):
        testcase_line = lines[end].strip()
        found_tc_size = False
        tc_size_line_num = None

        for i in range(end - 1, start - 1, -1):
            tc_size_match = tc_size_regex.search(lines[i])
            if tc_size_match:
                found_tc_size = True
                tc_size_line_num = i
                tc_size = tc_size_match.group(1).replace(" ", "").strip(':')
                if tc_size not in cls.allowed_sizes:
                    error_info = ErrorInfoBuilder() \
                        .set_buggy_file_path(relative_path) \
                        .set_defect_type("XTS.CHECK.TCSIZE.01") \
                        .set_description("测试用例规模注释@tc.size字段必填，且值在枚举范围内") \
                        .set_main_buggy_line(tc_size_line_num + 1) \
                        .set_main_buggy_code(lines[tc_size_line_num].strip()) \
                        .set_language(language) \
                        .build()
                    utils_data.write_error_to_json_file(error_info)
                    logger.info(json.dumps(error_info, indent=4, ensure_ascii=False))
                    return True
                break

        if not found_tc_size:
            error_info = ErrorInfoBuilder() \
                .set_buggy_file_path(relative_path) \
                .set_defect_type("XTS.CHECK.TCSIZE.01") \
                .set_description("测试用例规模注释@tc.size字段必填") \
                .set_main_buggy_line(end + 1) \
                .set_main_buggy_code(testcase_line) \
                .set_language(language) \
                .build()
            utils_data.write_error_to_json_file(error_info)
            logger.info(json.dumps(error_info, indent=4, ensure_ascii=False))
            return True
        return False

    @classmethod
    def check_tc_type_in_range(cls, lines, start, end, tc_type_regex, language, relative_path):
        testcase_line = lines[end].strip()
        found_tc_type = False
        tc_type_line_num = None

        for i in range(end - 1, start - 1, -1):
            tc_type_match = tc_type_regex.search(lines[i])
            if tc_type_match:
                found_tc_type = True
                tc_type_line_num = i
                tc_type = tc_type_match.group(1).replace(" ", "").strip(':')
                if tc_type not in cls.allowed_types:
                    error_info = ErrorInfoBuilder() \
                        .set_buggy_file_path(relative_path) \
                        .set_defect_type("XTS.CHECK.TCTYPE.01") \
                        .set_description("测试用例类型注释@tc.type字段必填，且值在枚举范围内") \
                        .set_main_buggy_line(tc_type_line_num + 1) \
                        .set_main_buggy_code(lines[tc_type_line_num].strip()) \
                        .set_language(language) \
                        .build()
                    utils_data.write_error_to_json_file(error_info)
                    logger.info(json.dumps(error_info, indent=4, ensure_ascii=False))
                    return True
                break

        if not found_tc_type:
            error_info = ErrorInfoBuilder() \
                .set_buggy_file_path(relative_path) \
                .set_defect_type("XTS.CHECK.TCTYPE.01") \
                .set_description("测试用例类型注释@tc.type字段必填") \
                .set_main_buggy_line(end + 1) \
                .set_main_buggy_code(testcase_line) \
                .set_language(language) \
                .build()
            utils_data.write_error_to_json_file(error_info)
            logger.info(json.dumps(error_info, indent=4, ensure_ascii=False))
            return True
        return False

    @classmethod
    def _find_testcases(cls, file_entities, language):
        for file_entity in file_entities:
            file_path = file_entity.full_path
            testcase_regex_to_use = cls.testcase_regex[language]
            try:
                with open(file_path, 'r', encoding='utf-8') as file:
                    lines = file.readlines()
                    testcases = []
                    start_line = 0
                    for i, line in enumerate(lines):
                        testcase_match = testcase_regex_to_use.search(line)
                        if testcase_match:
                            testcases.append((start_line, i, line))
                            start_line = i + 1
                    if not testcases:
                        continue
                    yield file_path, lines, testcases
            except Exception as e:
                logger.error(f"Error reading file {file_path}: {e}")

    @classmethod
    def check_tc_number(cls):
        has_error = False
        file_entities = utils_data.get_file()

        def get_language(file_name):
            for suffix, lang in cls.language_map.items():
                if file_name.endswith(suffix):
                    return lang
            return None

        for file_entity in file_entities:
            file_name = file_entity.file_name
            language = get_language(file_name)
            if language is None:
                continue
            for file_path, lines, testcases in cls._find_testcases([file_entity], language):
                if cls.check_duplicate_tc_numbers_in_file(lines, file_entity.file_path, language):
                    has_error = True
                if cls.check_duplicate_tc_numbers_with_siblings(lines, file_entity, language):
                    has_error = True
                for start, end, _ in testcases:
                    if cls.check_tc_number_in_range(lines, start, end, cls.tc_number_regex, language,
                                                    file_entity.file_path):
                        has_error = True
        if has_error:
            logger.info("测试用例@tc.number注释检查完成，错误信息已写入xts_result.json")
        else:
            logger.info("测试用例@tc.number注释检查通过")

    @classmethod
    def check_tc_name(cls):
        has_error = False
        file_entities = utils_data.get_file()

        def get_language(file_name):
            for suffix, lang in cls.language_map.items():
                if file_name.endswith(suffix):
                    return lang
            return None

        for file_entity in file_entities:
            file_name = file_entity.file_name
            language = get_language(file_name)
            if language is None:
                continue
            for file_path, lines, testcases in cls._find_testcases([file_entity], language):
                for start, end, _ in testcases:
                    if cls.check_tc_name_in_range(lines, start, end, cls.tc_name_regex, language,
                                                  file_entity.file_path):
                        has_error = True
        if has_error:
            logger.info("测试用例@tc.name注释检查完成，错误信息已写入xts_result.json")
        else:
            logger.info("测试用例@tc.name注释检查通过")

    @classmethod
    def check_tc_desc(cls):
        has_error = False
        file_entities = utils_data.get_file()

        def get_language(file_name):
            for suffix, lang in cls.language_map.items():
                if file_name.endswith(suffix):
                    return lang
            return None

        for file_entity in file_entities:
            file_name = file_entity.file_name
            language = get_language(file_name)
            if language is None:
                continue
            for file_path, lines, testcases in cls._find_testcases([file_entity], language):
                for start, end, _ in testcases:
                    if cls.check_tc_desc_in_range(lines, start, end, cls.tc_desc_regex, language,
                                                  file_entity.file_path):
                        has_error = True
        if has_error:
            logger.info("测试用例@tc.desc注释检查完成，错误信息已写入xts_result.json")
        else:
            logger.info("测试用例@tc.desc注释检查通过")

    @classmethod
    def check_tc_level(cls):
        has_error = False
        file_entities = utils_data.get_file()

        def get_language(file_name):
            for suffix, lang in cls.language_map.items():
                if file_name.endswith(suffix):
                    return lang
            return None

        for file_entity in file_entities:
            file_name = file_entity.file_name
            language = get_language(file_name)
            if language is None:
                continue
            for file_path, lines, testcases in cls._find_testcases([file_entity], language):
                for start, end, _ in testcases:
                    if cls.check_tc_level_in_range(lines, start, end, cls.tc_level_regex, language,
                                                   file_entity.file_path):
                        has_error = True
        if has_error:
            logger.info("测试用例@tc.level注释检查完成，错误信息已写入xts_result.json")
        else:
            logger.info("测试用例@tc.level注释检查通过")

    @classmethod
    def check_tc_size(cls):
        has_error = False
        file_entities = utils_data.get_file()

        def get_language(file_name):
            for suffix, lang in cls.language_map.items():
                if file_name.endswith(suffix):
                    return lang
            return None

        for file_entity in file_entities:
            file_name = file_entity.file_name
            language = get_language(file_name)
            if language is None:
                continue
            for file_path, lines, testcases in cls._find_testcases([file_entity], language):
                for start, end, _ in testcases:
                    if cls.check_tc_size_in_range(lines, start, end, cls.tc_size_regex, language,
                                                  file_entity.file_path):
                        has_error = True
        if has_error:
            logger.info("测试用例@tc.size注释检查完成，错误信息已写入xts_result.json")
        else:
            logger.info("测试用例@tc.size注释检查通过")

    @classmethod
    def check_tc_type(cls):
        has_error = False
        file_entities = utils_data.get_file()

        def get_language(file_name):
            for suffix, lang in cls.language_map.items():
                if file_name.endswith(suffix):
                    return lang
            return None

        for file_entity in file_entities:
            file_name = file_entity.file_name
            language = get_language(file_name)
            if language is None:
                continue
            for file_path, lines, testcases in cls._find_testcases([file_entity], language):
                for start, end, _ in testcases:
                    if cls.check_tc_type_in_range(lines, start, end, cls.tc_type_regex, language,
                                                  file_entity.file_path):
                        has_error = True
        if has_error:
            logger.info("测试用例@tc.type注释检查完成，错误信息已写入xts_result.json")
        else:
            logger.info("测试用例@tc.type注释检查通过")
